package cntr;

import bbcom.*;
import bbcom.interfaces.IContainer;
import bbcom.interfaces.IImplementationParser;
import bbcom.interfaces.IPropertiesParser;
import bbcom.interfaces.IUserInterfaceParser;
import cntr.memoryspace.EventChannelMemoryObject;
import cntr.memoryspace.MemoryObject;
import cntr.notifications.DeploymentCompletionNotification;
import cntr.notifications.MethodInvocationNotification;
import cntr.proxies.EventChannelProxy;
import cntr.proxies.RequiredRoleProxy;
import cntr.memoryspace.ComponentInstanceMemoryObject;
import cntr.memoryspace.SystemMemorySpace;
import cntr.proxies.SourceRoleProxy;
import cntr.proxies.WebServiceUserInterfaceProxy;
import cntr.symboltable.*;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.jgroups.*;
import org.jgroups.blocks.MethodCall;
import org.jgroups.blocks.RequestOptions;
import org.jgroups.blocks.ResponseMode;
import org.jgroups.blocks.RpcDispatcher;
import org.jgroups.protocols.*;
import org.jgroups.protocols.pbcast.GMS;
import org.jgroups.protocols.pbcast.NAKACK2;
import org.jgroups.protocols.pbcast.STABLE;
import org.jgroups.stack.Protocol;

import javax.jws.WebService;
import javax.xml.ws.Endpoint;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;


/**
 * Created by Thanh-Trung Pham.
 */
public class JGbContainer extends ReceiverAdapter implements IContainer {
    private static final String JGBCONTAINER = "/jgbcontainer";
    private static final String JGBCONTAINER_WS = "/ws/jgbcontainer/";

    public static final String SETTER_METHOD_PREFIX = "set";
    public static final int RPC_TIMEOUT = 20000;

    private String m_strSystemName;
    private DeviceContainerType m_oDeviceContainerType;
    private String m_strBaseFolderName;

    private GlobalScope m_oGlobalScope;
    private SymbolTable m_oSymbolTable;
    private SystemMemorySpace m_oSystemMemorySpace;

    private JChannel m_oJChannel;
    private RpcDispatcher m_oRpcDispatcher;
    private HttpServer m_oHTTPServer;

    private List<String> m_oCompletedDeviceContainers = new ArrayList<>(); //which device containers have completed their deployment.
    private Map<String, Address> m_oDeviceContainersJGbAddresses = new HashMap<>(); //Device containers' names and their JGroup-based addresses
    private Map<String, String> m_oAllDeviceContainersIPAddresses; //Device containers' names and their IP addresses

    private BlockingQueue<Runnable> m_oRunnableTasksQueue = new LinkedBlockingDeque<>();
    private ThreadPoolExecutor m_oThreadPoolExecutor;

    private boolean m_isLocalDeploymentCompleted = false;
    private boolean m_isGlobalDeploymentCompleted = false;

    private IImplementationParser m_oImplementationParserPlugin;
    private IUserInterfaceParser m_oUserInterfaceParserPlugin;
    private IPropertiesParser m_oPropertiesParserPlugin;

    /**
     * The constructor.
     */
    public JGbContainer() {

    }

    @Override
    public void setConfigurationSettings(String systemName, DeviceContainerType deviceContainerType, Map<String, String> allDeviceContainersIPAddresses,
                                         String baseFolderName) throws Exception {
        m_strSystemName = systemName;
        m_oDeviceContainerType = deviceContainerType;
        m_strBaseFolderName = baseFolderName;

        m_oAllDeviceContainersIPAddresses = allDeviceContainersIPAddresses;

        initializeSymbolTable();
        initializeMemorySpace();
        initializeThreadPoolExecutor();
        initializeJChannel();
    }

    @Override
    public void setPlugins(IImplementationParser implementationParserPlugin, IUserInterfaceParser userInterfaceParserPlugin, IPropertiesParser propertiesParserPlugin) throws Exception {
        m_oImplementationParserPlugin = implementationParserPlugin;
        m_oUserInterfaceParserPlugin = userInterfaceParserPlugin;
        m_oPropertiesParserPlugin = propertiesParserPlugin;

        initializeHTTPServer();
    }

    /**
     * Initialize the thread pool executor.
     */
    private void initializeThreadPoolExecutor() {
        m_oThreadPoolExecutor = new ThreadPoolExecutor(3, 6, 1000, TimeUnit.MILLISECONDS, m_oRunnableTasksQueue);
        m_oThreadPoolExecutor.prestartAllCoreThreads();
    }

    /**
     * Initialize the HTTP server.
     *
     * @throws Exception if there is an error.
     */
    private void initializeHTTPServer() throws Exception {
        int iHttpPort = Integer.parseInt(m_oPropertiesParserPlugin.getProperty(m_oDeviceContainerType.getProperties(), "httpPort"));

        m_oHTTPServer = HttpServer.create(new InetSocketAddress(iHttpPort), 0);
        m_oHTTPServer.createContext(JGBCONTAINER, new HttpHandler() {
            @Override
            public void handle(HttpExchange httpExchange) throws IOException {
                String strRequestQuery = httpExchange.getRequestURI().getQuery();
                Map oRequestParameters = parseRequestQuery(strRequestQuery);
                if (oRequestParameters.containsKey("stop")) {
                    String strResponseContent = "The JGbContainer '" + getContainerName() + "' is about to stop.";
                    writeResponseContent(httpExchange, strResponseContent);
                    stop();
                } else {
                    String strResponseContent = "The JGbContainer '" + getContainerName() + "' is running.";
                    writeResponseContent(httpExchange, strResponseContent);
                }
            }

            /**
             * Write out the response content.
             * @param httpExchange the given http exchange.
             * @param responseContent the content for the reponse.
             * @throws IOException if there is an error.
             */
            private void writeResponseContent(HttpExchange httpExchange, String responseContent) throws IOException {
                httpExchange.sendResponseHeaders(200, responseContent.length());
                OutputStream oResponseBody = httpExchange.getResponseBody();
                oResponseBody.write(responseContent.getBytes());

                oResponseBody.close();
            }

            /**
             * Parse the request query.
             * @param requestQuery the request query.
             * @return the name value pairs.
             */
            private Map parseRequestQuery(String requestQuery) {
                Map<String, String> oResultParameters = new HashMap<>();

                if (requestQuery == null) {
                    return oResultParameters;
                }

                for (String strParameter : requestQuery.split("&")) {
                    String[] strNameValuePair = strParameter.split("=");
                    if (strNameValuePair.length > 1) {
                        oResultParameters.put(strNameValuePair[0], strNameValuePair[1]);
                    } else {
                        oResultParameters.put(strNameValuePair[0], "");

                    }
                }

                return oResultParameters;
            }
        });


        m_oHTTPServer.setExecutor(null);
        m_oHTTPServer.start();

    }

    /**
     * Initialize the JChannel.
     *
     * @throws Exception if there is an error.
     */
    private void initializeJChannel() throws Exception {

        int iBindPort = 7800;

        System.setProperty("java.net.preferIPv4Stack", "true");
        System.setProperty("jgroups.bind_addr", m_oAllDeviceContainersIPAddresses.get(getContainerName()));

        StringBuilder oContainersIPAddressesAndPorts = new StringBuilder();
        for (String strContainerIPAddress : m_oAllDeviceContainersIPAddresses.values()) {
            oContainersIPAddressesAndPorts.append(strContainerIPAddress).append("[").append(iBindPort).append("],");
        }

        System.setProperty("jgroups.tcpping.initial_hosts", oContainersIPAddressesAndPorts.toString());
        Protocol[] oProtocolStack = {
                new TCP().setValue("bind_port", iBindPort),
                new TCPPING().setValue("max_dynamic_hosts", 0),
                new MERGE3(),
                new FD_SOCK(),
                new FD(),
                new VERIFY_SUSPECT(),
                new BARRIER(),
                new NAKACK2().setValue("use_mcast_xmit", false),
                new UNICAST3(),
                new STABLE(),
                new GMS(),
                new MFC(),
                new FRAG2()
        };

        m_oJChannel = new JChannel(oProtocolStack);
        //m_oJChannel = new JChannel("tcp.xml");
        m_oJChannel.setName(m_oDeviceContainerType.getName());

        m_oRpcDispatcher = new RpcDispatcher(m_oJChannel, this);

        m_oRpcDispatcher.setMembershipListener(this);
        m_oRpcDispatcher.setMessageListener(this);
        //m_oJChannel.setDiscardOwnMessages(true);
        m_oJChannel.connect(m_strSystemName);
    }

    /**
     * Initialize the memory space.
     */
    private void initializeMemorySpace() {
        m_oSystemMemorySpace = new SystemMemorySpace(m_strSystemName);
    }

    /**
     * Initialize the symbol table.
     */
    private void initializeSymbolTable() {
        m_oSymbolTable = new SymbolTable();
        m_oGlobalScope = m_oSymbolTable.getGlobalScope();
        m_oGlobalScope.define(new SystemSymbol(m_strSystemName));
    }

    @Override
    public void loadDependencies(List<String> dependencies) throws Exception {
        if (dependencies == null) {
            return;
        }

        List<URL> oURLs = new ArrayList<>();

        FilenameFilter oFilter = new FilenameFilter() {
            public boolean accept(File file, String name) {
                return name.endsWith(".jar");
            }
        };

        for (int i = 0; i < dependencies.size(); i++) {
            File oFile = new File(dependencies.get(i));
            if (!oFile.isAbsolute()) {
                oFile = new File(m_strBaseFolderName, dependencies.get(i));
            }

            if (!oFile.exists()) {
                continue;
            }

            if (oFile.isFile()) {
                oURLs.add(oFile.toURI().toURL());
            } else {
                File[] oJarFiles = oFile.listFiles(oFilter);
                for (int j = 0; j < oJarFiles.length; j++) {
                    oURLs.add(oJarFiles[j].toURI().toURL());
                }
            }
        }

        URLClassLoader oSystemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        Class<URLClassLoader> oURLClassLoaderClass = URLClassLoader.class;
        Method oAddURLMethod = oURLClassLoaderClass.getDeclaredMethod("addURL", new Class[]{URL.class});
        oAddURLMethod.setAccessible(true);
        for (int i = 0; i < oURLs.size(); i++) {
            oAddURLMethod.invoke(oSystemClassLoader, new Object[]{oURLs.get(i)});
        }
    }


    @Override
    public void createComponentInstance(ComponentInstanceType componentInstanceType) throws Exception {
        SystemSymbol oSystemSymbol = (SystemSymbol) m_oGlobalScope.resolve(m_strSystemName);
        String strComponentInstanceName = componentInstanceType.getName();
        oSystemSymbol.define(new ComponentInstanceSymbol(strComponentInstanceName, componentInstanceType));

        String strComponentName = componentInstanceType.getRefComponent();

        PrimitiveComponentSymbol oPrimitiveComponentSymbol = (PrimitiveComponentSymbol) m_oGlobalScope.resolve(strComponentName);
        PrimitiveComponentType oPrimitiveComponentType = oPrimitiveComponentSymbol.getPrimitiveComponentType();

        ImplementationType oImplementation = oPrimitiveComponentType.getImplementation();

        String strImplementationClassName = m_oImplementationParserPlugin.getImplementationClass(oImplementation);

        Class<?> oComponentClass = Class.forName(strImplementationClassName);
        Object oComponentInstanceObject = oComponentClass.newInstance();

        performComponentInstanceConfigurations(componentInstanceType, oPrimitiveComponentType, oComponentClass, oComponentInstanceObject);

        m_oSystemMemorySpace.put(strComponentInstanceName, new ComponentInstanceMemoryObject(strComponentInstanceName, oComponentInstanceObject));
    }

    @Override
    public void createEventChannel(EventChannelType eventChannelType) throws Exception {
        SystemSymbol oSystemSymbol = (SystemSymbol) m_oGlobalScope.resolve(m_strSystemName);
        String strEventChannelName = eventChannelType.getName();

        oSystemSymbol.define(new EventChannelSymbol(strEventChannelName, eventChannelType));
        String strRefEventGroup = eventChannelType.getRefEventGroup();
        EventGroupSymbol oEventGroupSymbol = (EventGroupSymbol) m_oGlobalScope.resolve(strRefEventGroup);
        EventGroupType oEventGroupType = oEventGroupSymbol.getEventGroupType();

        String strImplementationClassName = m_oImplementationParserPlugin.getImplementationClass(oEventGroupType.getImplementation());
        Object oEventChannelProxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Class.forName(strImplementationClassName)}, new EventChannelProxy(this));

        m_oSystemMemorySpace.put(strEventChannelName, new EventChannelMemoryObject(strEventChannelName, oEventChannelProxy));
    }


    /**
     * Perform the configurations for the given component instance.
     *
     * @param componentInstanceType   the given component instance type.
     * @param primitiveComponentType  the given primitive component type.
     * @param componentClass          the given component class.
     * @param componentInstanceObject the given component instance object.
     * @throws Exception if there is an error.
     */
    private void performComponentInstanceConfigurations(ComponentInstanceType componentInstanceType, PrimitiveComponentType primitiveComponentType, Class<?> componentClass, Object componentInstanceObject) throws Exception {
        for (int i = 0; i < componentInstanceType.getComponentConfigurationArray().length; i++) {
            ComponentConfigurationType oComponentConfiguration = componentInstanceType.getComponentConfigurationArray(i);

            String strRefComponentParameterName = oComponentConfiguration.getRefComponentParameter();
            String strValue = oComponentConfiguration.getValue();

            ComponentParameterType oComponentParameter = primitiveComponentType.getComponentParameterArray(i);
            String strComponentParameterName = oComponentParameter.getName();
            ParameterType.Type.Enum oComponentParameterType = oComponentParameter.getType();

            if (!strRefComponentParameterName.equals(strComponentParameterName)) {
                //TODO: To support the difference in the sequences of component parameters and component configurations.
                continue;
            }

            String strSetterMethodName = SETTER_METHOD_PREFIX + strComponentParameterName;
            if (oComponentParameterType == ParameterType.Type.BOOL) {
                Method oMethod = componentClass.getMethod(strSetterMethodName, new Class[]{boolean.class});
                oMethod.invoke(componentInstanceObject, new Object[]{Boolean.valueOf(strValue)});
            } else if (oComponentParameterType == ParameterType.Type.INT) {
                Method oMethod = componentClass.getMethod(strSetterMethodName, new Class[]{int.class});
                oMethod.invoke(componentInstanceObject, new Object[]{Integer.valueOf(strValue)});
            } else if (oComponentParameterType == ParameterType.Type.DOUBLE) {
                Method oMethod = componentClass.getMethod(strSetterMethodName, new Class[]{double.class});
                oMethod.invoke(componentInstanceObject, new Object[]{Double.valueOf(strValue)});
            } else if (oComponentParameterType == ParameterType.Type.STRING) {
                Method oMethod = componentClass.getMethod(strSetterMethodName, new Class[]{String.class});
                oMethod.invoke(componentInstanceObject, new Object[]{strValue});
            } else if (oComponentParameterType == ParameterType.Type.SOURCE_CODE) {
                //TODO: To support a source code evaluation.
            }
        }
    }


    @Override
    public void registerInterfaces(List<InterfaceType> interfaceTypes) {
        for (InterfaceType oInterfaceType : interfaceTypes) {
            m_oGlobalScope.define(new InterfaceSymbol(oInterfaceType.getName(), oInterfaceType));
        }
    }

    @Override
    public void registerEventGroups(List<EventGroupType> eventGroupTypes) {
        for (EventGroupType oEventGroup : eventGroupTypes) {
            m_oGlobalScope.define(new EventGroupSymbol(oEventGroup.getName(), oEventGroup));

        }
    }

    @Override
    public void registerComponents(List<PrimitiveComponentType> primitiveComponentTypes) {
        for (PrimitiveComponentType oPrimitiveComponentType : primitiveComponentTypes) {
            m_oGlobalScope.define(new PrimitiveComponentSymbol(oPrimitiveComponentType.getName(), oPrimitiveComponentType));

        }
    }


    @Override
    public void createConnector(String requiredComponentInstanceName, String requiredRoleName, String interfaceName, String providedComponentInstanceName, String deviceContainerNameOfProvidedComponentInstance) throws Exception {
        ComponentInstanceMemoryObject oRequiredComponentInstanceMemoryObject = (ComponentInstanceMemoryObject) m_oSystemMemorySpace.get(requiredComponentInstanceName);
        Object oRequiredComponentInstanceObject = oRequiredComponentInstanceMemoryObject.getValue();

        InterfaceSymbol oInterfaceSymbol = (InterfaceSymbol) m_oGlobalScope.resolve(interfaceName);
        ImplementationType oInterfaceImplementation = oInterfaceSymbol.getInterfaceType().getImplementation();


        String strInterfaceImplementationClass = m_oImplementationParserPlugin.getImplementationClass(oInterfaceImplementation);
        Method oMethod = oRequiredComponentInstanceObject.getClass().getMethod(SETTER_METHOD_PREFIX + requiredRoleName, new Class[]{Class.forName(strInterfaceImplementationClass)});

        if (deviceContainerNameOfProvidedComponentInstance.equals(m_oDeviceContainerType.getName())) {
            ComponentInstanceMemoryObject oProvidedComponentInstanceMemoryObject = (ComponentInstanceMemoryObject) m_oSystemMemorySpace.get(providedComponentInstanceName);
            Object oProvidedComponentInstanceObject = oProvidedComponentInstanceMemoryObject.getValue();
            oMethod.invoke(oRequiredComponentInstanceObject, new Object[]{oProvidedComponentInstanceObject});
        } else {
            RequiredRoleProxy oRequiredRoleProxy = new RequiredRoleProxy(this, deviceContainerNameOfProvidedComponentInstance, providedComponentInstanceName);
            Object oProxyObject = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Class.forName(strInterfaceImplementationClass)}, oRequiredRoleProxy);
            oMethod.invoke(oRequiredComponentInstanceObject, new Object[]{oProxyObject});
        }
    }

    @Override
    public void createEventConnector(String sourceComponentInstanceName, String sourceRoleName, String eventGroupName, String sinkComponentInstanceName, String deviceContainerNameOfSinkComponentInstance) throws Exception {
        ComponentInstanceMemoryObject oSourceComponentInstanceMemoryObject = (ComponentInstanceMemoryObject) m_oSystemMemorySpace.get(sourceComponentInstanceName);
        Object oSourceComponentInstanceObject = oSourceComponentInstanceMemoryObject.getValue();

        EventGroupSymbol oEventGroupSymbol = (EventGroupSymbol) m_oGlobalScope.resolve(eventGroupName);
        ImplementationType oEventGroupImplementation = oEventGroupSymbol.getEventGroupType().getImplementation();

        String strEventGroupImplementationClass = m_oImplementationParserPlugin.getImplementationClass(oEventGroupImplementation);
        Method oMethod = oSourceComponentInstanceObject.getClass().getMethod(SETTER_METHOD_PREFIX + sourceRoleName, new Class[]{Class.forName(strEventGroupImplementationClass)});


        SourceRoleProxy oSourceRoleProxy = oSourceComponentInstanceMemoryObject.getSourceRoleProxy(sourceRoleName);
        if (oSourceRoleProxy == null) {
            oSourceRoleProxy = new SourceRoleProxy(this);
            oSourceComponentInstanceMemoryObject.putSourceRoleProxy(sourceRoleName, oSourceRoleProxy);
            Object oProxyObject = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Class.forName(strEventGroupImplementationClass)}, oSourceRoleProxy);
            oMethod.invoke(oSourceComponentInstanceObject, new Object[]{oProxyObject});
        }

        oSourceRoleProxy.addSinkContainerNameAndSinkDeployableEntityName(deviceContainerNameOfSinkComponentInstance, sinkComponentInstanceName);
    }

    @Override
    public void createChannelSourceConnector(String sourceComponentInstanceName, String sourceRoleName, String eventGroupName, String eventChannelName, String deviceContainerNameOfEventChannel) throws Exception {
        createEventConnector(sourceComponentInstanceName, sourceRoleName, eventGroupName, eventChannelName, deviceContainerNameOfEventChannel);
    }

    @Override
    public void createChannelSinkConnector(String eventChannelName, String sinkComponentInstanceName, String deviceContainerNameOfSinkComponentInstance, FilterRuleType filterRule) throws Exception {
        EventChannelMemoryObject oEventChannelMemoryObject = (EventChannelMemoryObject) m_oSystemMemorySpace.get(eventChannelName);

        Object oFilterRuleObject = null;
        if (filterRule != null) {
            ImplementationType oFilterRuleImplementation = filterRule.getImplementation();
            String strFilterRuleImplementationClassName = m_oImplementationParserPlugin.getImplementationClass(oFilterRuleImplementation);
            oFilterRuleObject = Class.forName(strFilterRuleImplementationClassName).newInstance();
        }

        EventChannelProxy oEventChannelProxy = (EventChannelProxy) Proxy.getInvocationHandler(oEventChannelMemoryObject.getValue());
        oEventChannelProxy.addSinkContainerNameSinkDeployableEntityNameAndFilter(deviceContainerNameOfSinkComponentInstance, sinkComponentInstanceName, oFilterRuleObject);
    }

    @Override
    public void informLocalDeploymentCompletion() {
        m_isLocalDeploymentCompleted = true;
        informAllContainersAboutDeploymentProgress();
    }

    /**
     * Create a web service annotation for a class.
     *
     * @param clazz                  the given class.
     * @param endPointInterfaceValue the 'endPointInterface' value for the web service annotation.
     * @throws Exception if there is an error.
     */
    private void createWebServiceAnnotationForClass(Class clazz, final String endPointInterfaceValue) throws Exception {
        final Class<? extends Annotation> oWebServiceAnnotationTypeClass = WebServiceUserInterfaceProxy.class.getAnnotation(WebService.class).annotationType();

        Annotation oUpdatedAnnotation = new WebService() {
            @Override
            public Class<? extends Annotation> annotationType() {
                return oWebServiceAnnotationTypeClass;
            }

            @Override
            public String name() {
                return "";
            }

            @Override
            public String targetNamespace() {
                return "";
            }

            @Override
            public String serviceName() {
                return "";
            }

            @Override
            public String portName() {
                return "";
            }

            @Override
            public String wsdlLocation() {
                return "";
            }

            @Override
            public String endpointInterface() {
                return endPointInterfaceValue;
            }
        };

        Field oAnnotationsField = Class.class.getDeclaredField("annotations");
        //Field oAnnotationsField = Class.class.getDeclaredField("declaredAnnotations");
        oAnnotationsField.setAccessible(true);
        Map<Class<? extends Annotation>, Annotation> oAnnotations = (Map<Class<? extends Annotation>, Annotation>) oAnnotationsField.get(clazz);
        if (oAnnotations == null || (oAnnotations.size() == 0)) {
            oAnnotations = new HashMap<>();
            oAnnotationsField.set(clazz, oAnnotations);
        }

        oAnnotations.put(WebService.class, oUpdatedAnnotation);
    }


    /**
     * Back up the annotations for a class.
     *
     * @param clazz the given class.
     * @return the backed up annotations.
     * @throws Exception if there is an error.
     */
    private Object backupAnnotationsForClass(Class clazz) throws Exception {
        Field oAnnotationsField = Class.class.getDeclaredField("annotations");
        oAnnotationsField.setAccessible(true);

        return oAnnotationsField.get(clazz);
    }

    /**
     * Restore the annotations for a class.
     *
     * @param clazz the given class.
     * @return the annotations to restore.
     * @throws Exception if there is an error.
     */
    private void restoreAnnotationsForClass(Class clazz, Object annotations) throws Exception {
        Field oAnnotationsField = Class.class.getDeclaredField("annotations");
        oAnnotationsField.setAccessible(true);

        oAnnotationsField.set(clazz, annotations);
    }

    /**
     * Check if the given user interface type is the web service type.
     *
     * @param userInterfaceType the given user interface type.
     * @return true if the given user interface type is the web service type, otherwise, false.
     * @throws Exception if there is an error.
     */
    private boolean checkIfUserInterfaceTypeIsWebService(UserInterfaceType userInterfaceType) throws Exception {
        if ("webservice".equals(m_oUserInterfaceParserPlugin.getProperty(userInterfaceType, "ui.type"))) {
            return true;
        }

        return false;
    }

    @Override
    public void createUserInterfaceForSystemLevelProvidedRole(String systemLevelProvidedRoleName, String interfaceName, String providedComponentInstanceName, String providedRoleNameOfComponentInstance, UserInterfaceType userInterfaceType) throws Exception {
        if (!checkIfUserInterfaceTypeIsWebService(userInterfaceType)) {
            return;
        }

        ComponentInstanceMemoryObject oComponentInstanceMemoryObject = (ComponentInstanceMemoryObject) m_oSystemMemorySpace.get(providedComponentInstanceName);
        Object oComponentInstanceObject = oComponentInstanceMemoryObject.getValue();

        InterfaceSymbol oInterfaceSymbol = (InterfaceSymbol) m_oGlobalScope.resolve(interfaceName);
        ImplementationType oInterfaceImplementation = oInterfaceSymbol.getInterfaceType().getImplementation();
        String strInterfaceImplementationClassName = m_oImplementationParserPlugin.getImplementationClass(oInterfaceImplementation);
        Class<?> oInterfaceImplementationClass = Class.forName(strInterfaceImplementationClassName);

        Object oBackedUpAnnotationsOfInterfaceImplementationClass = backupAnnotationsForClass(oInterfaceImplementationClass);
        Object oBackedUpAnnotationsOfComponentImplementationClass = backupAnnotationsForClass(oComponentInstanceObject.getClass());

        createWebServiceAnnotationForClass(oInterfaceImplementationClass, "");
        createWebServiceAnnotationForClass(oComponentInstanceObject.getClass(), strInterfaceImplementationClassName);

        HttpContext oHttpContext = m_oHTTPServer.createContext(JGBCONTAINER_WS + systemLevelProvidedRoleName);
        Endpoint.create(oComponentInstanceObject).publish(oHttpContext);

        restoreAnnotationsForClass(oInterfaceImplementationClass, oBackedUpAnnotationsOfInterfaceImplementationClass);
        restoreAnnotationsForClass(oComponentInstanceObject.getClass(), oBackedUpAnnotationsOfComponentImplementationClass);
    }


    @Override
    public void createUserInterfaceForSystemLevelSinkRole(String systemLevelSinkRoleName, String eventGroupName, String sinkComponentInstanceName, String sinkRoleNameOfComponentInstance, UserInterfaceType userInterfaceType) throws Exception {
        if (!checkIfUserInterfaceTypeIsWebService(userInterfaceType)) {
            return;
        }
        ComponentInstanceMemoryObject oComponentInstanceMemoryObject = (ComponentInstanceMemoryObject) m_oSystemMemorySpace.get(sinkComponentInstanceName);
        Object oComponentInstanceObject = oComponentInstanceMemoryObject.getValue();

        EventGroupSymbol oEventGroupSymbol = (EventGroupSymbol) m_oGlobalScope.resolve(eventGroupName);
        ImplementationType oEventGroupImplementation = oEventGroupSymbol.getEventGroupType().getImplementation();
        String strEventGroupImplementationClassName = m_oImplementationParserPlugin.getImplementationClass(oEventGroupImplementation);
        Class<?> oEventGroupImplementationClass = Class.forName(strEventGroupImplementationClassName);

        Object oBackedUpAnnotationsOfEventGroupImplementationClass = backupAnnotationsForClass(oEventGroupImplementationClass);
        Object oBackedUpAnnotationsOfComponentImplementationClass = backupAnnotationsForClass(oComponentInstanceObject.getClass());

        createWebServiceAnnotationForClass(oEventGroupImplementationClass, "");
        createWebServiceAnnotationForClass(oComponentInstanceObject.getClass(), strEventGroupImplementationClassName);

        HttpContext oHttpContext = m_oHTTPServer.createContext(JGBCONTAINER_WS + systemLevelSinkRoleName);
        Endpoint.create(oComponentInstanceObject).publish(oHttpContext);

        restoreAnnotationsForClass(oEventGroupImplementationClass, oBackedUpAnnotationsOfEventGroupImplementationClass);
        restoreAnnotationsForClass(oComponentInstanceObject.getClass(), oBackedUpAnnotationsOfComponentImplementationClass);
    }


    /**
     * Inform component instances that all local deployments have completed.
     */
    private void informComponentInstancesThatAllLocalDeploymentsHaveCompleted() {
        for (MemoryObject oMemoryObject : m_oSystemMemorySpace.getAllMemoryObjects()) {
            if (oMemoryObject instanceof ComponentInstanceMemoryObject) {
                Object oComponentInstanceObject = oMemoryObject.getValue();

                try {
                    Method oMethod = oComponentInstanceObject.getClass().getMethod("start", new Class[]{});
                    oMethod.invoke(oComponentInstanceObject, new Object[]{});
                } catch (Exception oException) {
                    //TODO: log into log files.
                    oException.printStackTrace();
                }

            }
        }
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }


    @Override
    public void stop() {
        informComponentInstancesThatContainerIsGoingToStop();

        m_oThreadPoolExecutor.shutdown();

        try {
            m_oThreadPoolExecutor.awaitTermination(3, TimeUnit.SECONDS);
        } catch (InterruptedException oIgnoredInterruptedException) {
        }

        m_oJChannel.close();
        m_oRpcDispatcher.stop();

        if (m_oHTTPServer != null) {
            m_oHTTPServer.stop(0);
            m_oHTTPServer = null;
        }

        System.exit(0);
    }

    /**
     * Inform component instances that the container is going to stop.
     */
    private void informComponentInstancesThatContainerIsGoingToStop() {
        Collection<MemoryObject> oMemoryObjects = m_oSystemMemorySpace.getAllMemoryObjects();
        for (MemoryObject oMemoryObject : oMemoryObjects) {
            if (oMemoryObject instanceof ComponentInstanceMemoryObject) {
                final Object oComponentInstanceObject = oMemoryObject.getValue();

                ExecutorService oExecutorService = Executors.newSingleThreadExecutor();
                final Future<Void> oFuture = oExecutorService.submit(new Callable<Void>() {

                    @Override
                    public Void call() throws Exception {

                        try {
                            Method oMethod = oComponentInstanceObject.getClass().getMethod("stop", new Class[]{});
                            oMethod.invoke(oComponentInstanceObject, new Object[]{});
                        } catch (Exception oException) {
                            //TODO: log into log files.
                            oException.printStackTrace();
                        }

                        return null;
                    }
                });

                try {
                    oFuture.get(3, TimeUnit.SECONDS);
                } catch (Exception oIgnoredException) {
                    oFuture.cancel(true);
                }

                oExecutorService.shutdownNow();
            }
        }
    }

    @Override
    public void viewAccepted(View view) {
        //System.out.println("Accepted view: " + view);
        final List<Address> oMemberAddresses = view.getMembers();
        m_oRunnableTasksQueue.add(new Runnable() {
            @Override
            public void run() {
                processViewAcceptedEvent(oMemberAddresses);
            }
        });
    }

    @Override
    public void receive(final Message message) {
        //System.out.println("Received message: " + message);
        m_oRunnableTasksQueue.add(new Runnable() {
            @Override
            public void run() {
                processIncomingMessage(message);
            }
        });


    }

    /**
     * Process the view accepted event.
     *
     * @param memberAddresses the addresses of the members of the view.
     */
    private void processViewAcceptedEvent(List<Address> memberAddresses) {
        buildTabletOfContainersJGbAddresses(memberAddresses);
        informAllContainersAboutDeploymentProgress();
    }

    /**
     * Inform all containers about the current deployment progress.
     */
    private void informAllContainersAboutDeploymentProgress() {
        if (!m_isGlobalDeploymentCompleted && m_isLocalDeploymentCompleted) {
            m_oRunnableTasksQueue.add(new Runnable() {
                @Override
                public void run() {
                    Message oMessage = new Message(null, null, new DeploymentCompletionNotification(getContainerJGbAddress(), getContainerName()));
                    processOutgoingMessage(oMessage);
                }
            });
        }
    }

    /**
     * Build up a table of containers' JGroup-based addresses.
     *
     * @param memberAddresses the addresses of the members of the view.
     */
    private void buildTabletOfContainersJGbAddresses(List<Address> memberAddresses) {
        synchronized (m_oDeviceContainersJGbAddresses) {
            m_oDeviceContainersJGbAddresses.clear();

            for (Address oAddress : memberAddresses) {
                m_oDeviceContainersJGbAddresses.put(oAddress.toString(), oAddress);
            }
        }


    }

    /**
     * Process the incoming message.
     *
     * @param message the incoming message.
     */
    private void processIncomingMessage(Message message) {
        if (message.getObject() instanceof DeploymentCompletionNotification) {
            if (m_isGlobalDeploymentCompleted) {
                return;
            }

            DeploymentCompletionNotification oDeploymentCompletionNotification = (DeploymentCompletionNotification) message.getObject();
            String strNodeName = oDeploymentCompletionNotification.getNodeName();

            if (!m_oCompletedDeviceContainers.contains(strNodeName)) {
                m_oCompletedDeviceContainers.add(strNodeName);
            }

            if (checkIfAllLocalDeploymentsHaveCompleted()) {
                m_isGlobalDeploymentCompleted = true;
                informComponentInstancesThatAllLocalDeploymentsHaveCompleted();
            }
        } else if (message.getObject() instanceof MethodInvocationNotification) {
            MethodInvocationNotification oMethodInvocationNotification = (MethodInvocationNotification) message.getObject();

            try {
                invokeMethodOfDeployableEntity(oMethodInvocationNotification.getDeployableEntityName(), oMethodInvocationNotification.getMethodCall());
            } catch (Exception oException) {
                //TODO: log into log files.
                oException.printStackTrace();
            }
        }

    }

    /**
     * Check if all the local deployment have completed.
     *
     * @return true if the local deployment have completed, false otherwise.
     */
    private boolean checkIfAllLocalDeploymentsHaveCompleted() {
        for (String strDeviceContainerName : m_oAllDeviceContainersIPAddresses.keySet()) {
            if (!m_oCompletedDeviceContainers.contains(strDeviceContainerName)) {
                return false;
            }
        }

        return true;
    }


    /**
     * Process the outgoing message.
     *
     * @param message the outgoing message.
     */
    private void processOutgoingMessage(Message message) {
        try {
            if (message.getObject() instanceof DeploymentCompletionNotification
                    || message.getObject() instanceof MethodInvocationNotification) {
                m_oJChannel.send(message);
                //System.out.println("Sent message: " + message);
            }
        } catch (Exception oException) {
            //TODO: Log into log files.
            oException.printStackTrace();
        }
    }

    /**
     * Get the container JGroup-based address.
     *
     * @return the the container JGroup-based address.
     */
    private Address getContainerJGbAddress() {
        return m_oJChannel.getAddress();
    }


    /**
     * Get the URL of the container.
     *
     * @return the URL of the container.
     */
    public URL getURL() {
        try {
            return new URL("http://localhost:" + m_oHTTPServer.getAddress().getPort() + JGBCONTAINER);
        } catch (MalformedURLException oIgnoredMalformedURLException) {
            return null;
        }
    }

    /**
     * Get the container name (a.k.a the node name).
     *
     * @return the container name.
     */
    public String getContainerName() {
        return m_oDeviceContainerType.getName();
    }

    /**
     * Get the system name (a.k.a the cluster name).
     *
     * @return the system name.
     */
    public String getSystemName() {
        return m_strSystemName;
    }

    /**
     * Get the container JGroup-based addresses.
     *
     * @return the container JGroup-based addresses.
     */
    public Map<String, Address> getContainerJGbAddresses() {
        return m_oDeviceContainersJGbAddresses;
    }

    /**
     * Handle RPC requests
     *
     * @param componentInstanceName the component instance name.
     * @param methodCall            the method call including the method and its arguments.
     * @return the result.
     * @throws Exception if there is an error.
     */
    @SuppressWarnings("unused")
    public Object handleRPCRequest(String componentInstanceName, MethodCall methodCall) throws Exception {
        return invokeMethodOfDeployableEntity(componentInstanceName, methodCall);
    }


    /**
     * Invoke a method of a deployable entity.
     *
     * @param deployableEntityName the deployable entity.
     * @param methodCall           the method to invoke and its arguments.
     * @return the result.
     * @throws Exception if there is an error.
     */
    private Object invokeMethodOfDeployableEntity(String deployableEntityName, MethodCall methodCall) throws Exception {
        MemoryObject oMemoryObject = m_oSystemMemorySpace.get(deployableEntityName);
        Method oMethod = methodCall.getMethod();
        return oMethod.invoke(oMemoryObject.getValue(), methodCall.getArgs());
    }

    /**
     * Invoke the RPC method.
     *
     * @param remoteContainerName         the remote container's name.
     * @param remoteComponentInstanceName the remote component instance's name.
     * @param method                      the method of the remote component instance.
     * @param args                        the arguments for the the method of the remote component instance.
     * @return the result.
     * @throws Exception if there is an error.
     */
    public Object invokeRPCMethod(String remoteContainerName, String remoteComponentInstanceName, Method method, Object[] args) throws Exception {
        Address oRemoteContainerAddress = m_oDeviceContainersJGbAddresses.get(remoteContainerName);

        RequestOptions oRequestOptions = new RequestOptions(ResponseMode.GET_ALL, RPC_TIMEOUT);

        MethodCall oMethodCall = new MethodCall(method, args);
        Object oResult = m_oRpcDispatcher.callRemoteMethod(oRemoteContainerAddress, "handleRPCRequest", new Object[]{remoteComponentInstanceName, oMethodCall},
                new Class[]{remoteComponentInstanceName.getClass(), oMethodCall.getClass()}, oRequestOptions);

        if (oResult instanceof Exception) {
            throw (Exception) oResult;
        }

        return oResult;
    }

    /**
     * Send messages for method invocations.
     *
     * @param sinkContainerNames        the names of the containers hosting the deployable entities playing the sink role.
     * @param sinkDeployableEntityNames the names of the deployable entities playing the sink role.
     * @param method                    the method to call
     * @param args                      the arguments for the method to call.
     */
    public void sendMethodInvocationMessages(List<String> sinkContainerNames, List<String> sinkDeployableEntityNames, Method method, Object[] args) {
        for (int i = 0; i < sinkContainerNames.size(); i++) {
            sendMethodInvocationMessage(sinkContainerNames.get(i), sinkDeployableEntityNames.get(i), method, args);
        }
    }

    /**
     * Send a message for a method invocation.
     *
     * @param sinkContainerName        the name of the container hosting the deployable entity playing the sink role.
     * @param sinkDeployableEntityName the name of the deployable entity playing the sink role.
     * @param method                   the method to call
     * @param args                     the arguments for the method to call.
     */
    public void sendMethodInvocationMessage(String sinkContainerName, String sinkDeployableEntityName, Method method, Object[] args) {
        final Message oMessage = new Message(m_oDeviceContainersJGbAddresses.get(sinkContainerName), null,
                new MethodInvocationNotification(getContainerJGbAddress(), sinkDeployableEntityName, new MethodCall(method, args)));

        m_oRunnableTasksQueue.add(new Runnable() {
            @Override
            public void run() {
                processOutgoingMessage(oMessage);
            }
        });
    }

}
